﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Media3D;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace TetrisExampleBlock
{
    /// <summary>
    /// Interaktionslogik für MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        double angle_flat = Math.PI * 3 / 2;
        double angle_up = 0;
        double distance = 20;

        public MainWindow()
        {
            InitializeComponent();

            Viewbox viewbox = new Viewbox();
            Content = viewbox;

            Viewport3D viewport3D = new Viewport3D();
            viewbox.Child = viewport3D;

            viewport3D.Height = 300;
            viewport3D.Width = 600;

            PerspectiveCamera camera = new PerspectiveCamera();
            camera.Position = new Point3D(0, 0, distance);
            camera.LookDirection = new Vector3D(0, 0, -distance);
            camera.UpDirection = new Vector3D(0, 1, 0);
            camera.FieldOfView = 50;

            viewport3D.Camera = camera;

            #region tetrisBlock
            {
                ModelVisual3D modelVisual3DMesh = new ModelVisual3D();
                viewport3D.Children.Add(modelVisual3DMesh);

                GeometryModel3D mesh = new GeometryModel3D();
                modelVisual3DMesh.Content = mesh;

                MeshGeometry3D meshGeometry3D = new MeshGeometry3D();
                mesh.Geometry = meshGeometry3D;

                // Form the mesh 

                // Font face
                meshGeometry3D.Positions.Add(new Point3D(0, 0, 0));
                meshGeometry3D.Positions.Add(new Point3D(0, 4, 0));
                meshGeometry3D.Positions.Add(new Point3D(2, 4, 0));

                meshGeometry3D.Positions.Add(new Point3D(2, 4, 0));
                meshGeometry3D.Positions.Add(new Point3D(2, 0, 0));
                meshGeometry3D.Positions.Add(new Point3D(0, 0, 0));

                meshGeometry3D.Positions.Add(new Point3D(2, 2, 0));
                meshGeometry3D.Positions.Add(new Point3D(2, 4, 0));
                meshGeometry3D.Positions.Add(new Point3D(4, 4, 0));

                meshGeometry3D.Positions.Add(new Point3D(4, 4, 0));
                meshGeometry3D.Positions.Add(new Point3D(4, 2, 0));
                meshGeometry3D.Positions.Add(new Point3D(2, 2, 0));

                // Back face
                meshGeometry3D.Positions.Add(new Point3D(0, 0, -2));
                meshGeometry3D.Positions.Add(new Point3D(0, 4, -2));
                meshGeometry3D.Positions.Add(new Point3D(2, 4, -2));

                meshGeometry3D.Positions.Add(new Point3D(2, 4, -2));
                meshGeometry3D.Positions.Add(new Point3D(2, 0, -2));
                meshGeometry3D.Positions.Add(new Point3D(0, 0, -2));

                meshGeometry3D.Positions.Add(new Point3D(2, 2, -2));
                meshGeometry3D.Positions.Add(new Point3D(2, 4, -2));
                meshGeometry3D.Positions.Add(new Point3D(4, 4, -2));

                meshGeometry3D.Positions.Add(new Point3D(4, 4, -2));
                meshGeometry3D.Positions.Add(new Point3D(4, 2, -2));
                meshGeometry3D.Positions.Add(new Point3D(2, 2, -2));

                // Left side face
                meshGeometry3D.Positions.Add(new Point3D(0, 0, -2));
                meshGeometry3D.Positions.Add(new Point3D(0, 4, -2));
                meshGeometry3D.Positions.Add(new Point3D(0, 4, 0));

                meshGeometry3D.Positions.Add(new Point3D(0, 4, 0));
                meshGeometry3D.Positions.Add(new Point3D(0, 0, 0));
                meshGeometry3D.Positions.Add(new Point3D(0, 0, -2));

                // Top side face
                meshGeometry3D.Positions.Add(new Point3D(0, 4, 0));
                meshGeometry3D.Positions.Add(new Point3D(0, 4, -2));
                meshGeometry3D.Positions.Add(new Point3D(4, 4, -2));

                meshGeometry3D.Positions.Add(new Point3D(4, 4, -2));
                meshGeometry3D.Positions.Add(new Point3D(4, 4, 0));
                meshGeometry3D.Positions.Add(new Point3D(0, 4, 0));

                // Bottom faces
                meshGeometry3D.Positions.Add(new Point3D(0, 0, 0));
                meshGeometry3D.Positions.Add(new Point3D(0, 0, -2));
                meshGeometry3D.Positions.Add(new Point3D(2, 0, -2));

                meshGeometry3D.Positions.Add(new Point3D(2, 0, -2));
                meshGeometry3D.Positions.Add(new Point3D(2, 0, 0));
                meshGeometry3D.Positions.Add(new Point3D(0, 0, 0));

                meshGeometry3D.Positions.Add(new Point3D(2, 2, 0));
                meshGeometry3D.Positions.Add(new Point3D(2, 2, -2));
                meshGeometry3D.Positions.Add(new Point3D(4, 2, -2));

                meshGeometry3D.Positions.Add(new Point3D(4, 2, -2));
                meshGeometry3D.Positions.Add(new Point3D(4, 2, 0));
                meshGeometry3D.Positions.Add(new Point3D(2, 2, 0));

                meshGeometry3D.Positions.Add(new Point3D(2, 0, 0));
                meshGeometry3D.Positions.Add(new Point3D(2, 2, 0));
                meshGeometry3D.Positions.Add(new Point3D(2, 2, -2));

                meshGeometry3D.Positions.Add(new Point3D(2, 2, -2));
                meshGeometry3D.Positions.Add(new Point3D(2, 0, -2));
                meshGeometry3D.Positions.Add(new Point3D(2, 0, 0));

                meshGeometry3D.Positions.Add(new Point3D(4, 2, 0));
                meshGeometry3D.Positions.Add(new Point3D(4, 4, 0));
                meshGeometry3D.Positions.Add(new Point3D(4, 4, -2));

                meshGeometry3D.Positions.Add(new Point3D(4, 4, -2));
                meshGeometry3D.Positions.Add(new Point3D(4, 2, -2));
                meshGeometry3D.Positions.Add(new Point3D(4, 2, 0));



                // Give the Triangle-Points some indices
                
                // Font face
                meshGeometry3D.TriangleIndices.Add(1);
                meshGeometry3D.TriangleIndices.Add(0);
                meshGeometry3D.TriangleIndices.Add(2);

                meshGeometry3D.TriangleIndices.Add(4);
                meshGeometry3D.TriangleIndices.Add(3);
                meshGeometry3D.TriangleIndices.Add(5);

                meshGeometry3D.TriangleIndices.Add(7);
                meshGeometry3D.TriangleIndices.Add(6);
                meshGeometry3D.TriangleIndices.Add(8);

                meshGeometry3D.TriangleIndices.Add(10);
                meshGeometry3D.TriangleIndices.Add(9);
                meshGeometry3D.TriangleIndices.Add(11);

                // Back face
                meshGeometry3D.TriangleIndices.Add(12);
                meshGeometry3D.TriangleIndices.Add(13);
                meshGeometry3D.TriangleIndices.Add(14);

                meshGeometry3D.TriangleIndices.Add(15);
                meshGeometry3D.TriangleIndices.Add(16);
                meshGeometry3D.TriangleIndices.Add(17);

                meshGeometry3D.TriangleIndices.Add(18);
                meshGeometry3D.TriangleIndices.Add(19);
                meshGeometry3D.TriangleIndices.Add(20);

                meshGeometry3D.TriangleIndices.Add(21);
                meshGeometry3D.TriangleIndices.Add(22);
                meshGeometry3D.TriangleIndices.Add(23);

                // left side face
                meshGeometry3D.TriangleIndices.Add(25);
                meshGeometry3D.TriangleIndices.Add(24);
                meshGeometry3D.TriangleIndices.Add(26);

                meshGeometry3D.TriangleIndices.Add(28);
                meshGeometry3D.TriangleIndices.Add(27);
                meshGeometry3D.TriangleIndices.Add(29);

                // top side face
                meshGeometry3D.TriangleIndices.Add(31);
                meshGeometry3D.TriangleIndices.Add(30);
                meshGeometry3D.TriangleIndices.Add(32);

                meshGeometry3D.TriangleIndices.Add(34);
                meshGeometry3D.TriangleIndices.Add(33);
                meshGeometry3D.TriangleIndices.Add(35);

                // bottom side faces

                // down
                meshGeometry3D.TriangleIndices.Add(36);
                meshGeometry3D.TriangleIndices.Add(37);
                meshGeometry3D.TriangleIndices.Add(38);

                meshGeometry3D.TriangleIndices.Add(39);
                meshGeometry3D.TriangleIndices.Add(40);
                meshGeometry3D.TriangleIndices.Add(41);

                meshGeometry3D.TriangleIndices.Add(42);
                meshGeometry3D.TriangleIndices.Add(43);
                meshGeometry3D.TriangleIndices.Add(44);

                meshGeometry3D.TriangleIndices.Add(45);
                meshGeometry3D.TriangleIndices.Add(46);
                meshGeometry3D.TriangleIndices.Add(47);

                // right
                meshGeometry3D.TriangleIndices.Add(49);
                meshGeometry3D.TriangleIndices.Add(48);
                meshGeometry3D.TriangleIndices.Add(50);

                meshGeometry3D.TriangleIndices.Add(52);
                meshGeometry3D.TriangleIndices.Add(51);
                meshGeometry3D.TriangleIndices.Add(53);

                meshGeometry3D.TriangleIndices.Add(55);
                meshGeometry3D.TriangleIndices.Add(54);
                meshGeometry3D.TriangleIndices.Add(56);

                meshGeometry3D.TriangleIndices.Add(58);
                meshGeometry3D.TriangleIndices.Add(57);
                meshGeometry3D.TriangleIndices.Add(59);

                // Set Normals to +z, the start camera position (Normals are for lighting)
                // Font face
                meshGeometry3D.Normals.Add(new Vector3D(0, 0, 1));
                meshGeometry3D.Normals.Add(new Vector3D(0, 0, 1));
                meshGeometry3D.Normals.Add(new Vector3D(0, 0, 1));

                meshGeometry3D.Normals.Add(new Vector3D(0, 0, 1));
                meshGeometry3D.Normals.Add(new Vector3D(0, 0, 1));
                meshGeometry3D.Normals.Add(new Vector3D(0, 0, 1));

                meshGeometry3D.Normals.Add(new Vector3D(0, 0, 1));
                meshGeometry3D.Normals.Add(new Vector3D(0, 0, 1));
                meshGeometry3D.Normals.Add(new Vector3D(0, 0, 1));

                meshGeometry3D.Normals.Add(new Vector3D(0, 0, 1));
                meshGeometry3D.Normals.Add(new Vector3D(0, 0, 1));
                meshGeometry3D.Normals.Add(new Vector3D(0, 0, 1));

                // Back face

                meshGeometry3D.Normals.Add(new Vector3D(0, 0, -1));
                meshGeometry3D.Normals.Add(new Vector3D(0, 0, -1));
                meshGeometry3D.Normals.Add(new Vector3D(0, 0, -1));

                meshGeometry3D.Normals.Add(new Vector3D(0, 0, -1));
                meshGeometry3D.Normals.Add(new Vector3D(0, 0, -1));
                meshGeometry3D.Normals.Add(new Vector3D(0, 0, -1));

                meshGeometry3D.Normals.Add(new Vector3D(0, 0, -1));
                meshGeometry3D.Normals.Add(new Vector3D(0, 0, -1));
                meshGeometry3D.Normals.Add(new Vector3D(0, 0, -1));

                meshGeometry3D.Normals.Add(new Vector3D(0, 0, -1));
                meshGeometry3D.Normals.Add(new Vector3D(0, 0, -1));
                meshGeometry3D.Normals.Add(new Vector3D(0, 0, -1));

                // Left side face

                meshGeometry3D.Normals.Add(new Vector3D(-1, 0, 0));
                meshGeometry3D.Normals.Add(new Vector3D(-1, 0, 0));
                meshGeometry3D.Normals.Add(new Vector3D(-1, 0, 0));

                meshGeometry3D.Normals.Add(new Vector3D(-1, 0, 0));
                meshGeometry3D.Normals.Add(new Vector3D(-1, 0, 0));
                meshGeometry3D.Normals.Add(new Vector3D(-1, 0, 0));

                // Top side face

                meshGeometry3D.Normals.Add(new Vector3D(0, 1, 0));
                meshGeometry3D.Normals.Add(new Vector3D(0, 1, 0));
                meshGeometry3D.Normals.Add(new Vector3D(0, 1, 0));

                meshGeometry3D.Normals.Add(new Vector3D(0, 1, 0));
                meshGeometry3D.Normals.Add(new Vector3D(0, 1, 0));
                meshGeometry3D.Normals.Add(new Vector3D(0, 1, 0));

                // Bottom faces

                meshGeometry3D.Normals.Add(new Vector3D(0, -1, 0));
                meshGeometry3D.Normals.Add(new Vector3D(0, -1, 0));
                meshGeometry3D.Normals.Add(new Vector3D(0, -1, 0));

                meshGeometry3D.Normals.Add(new Vector3D(0, -1, 0));
                meshGeometry3D.Normals.Add(new Vector3D(0, -1, 0));
                meshGeometry3D.Normals.Add(new Vector3D(0, -1, 0));

                meshGeometry3D.Normals.Add(new Vector3D(0, -1, 0));
                meshGeometry3D.Normals.Add(new Vector3D(0, -1, 0));
                meshGeometry3D.Normals.Add(new Vector3D(0, -1, 0));

                meshGeometry3D.Normals.Add(new Vector3D(0, -1, 0));
                meshGeometry3D.Normals.Add(new Vector3D(0, -1, 0));
                meshGeometry3D.Normals.Add(new Vector3D(0, -1, 0));

                meshGeometry3D.Normals.Add(new Vector3D(1, 0, 0));
                meshGeometry3D.Normals.Add(new Vector3D(1, 0, 0));
                meshGeometry3D.Normals.Add(new Vector3D(1, 0, 0));

                meshGeometry3D.Normals.Add(new Vector3D(1, 0, 0));
                meshGeometry3D.Normals.Add(new Vector3D(1, 0, 0));
                meshGeometry3D.Normals.Add(new Vector3D(1, 0, 0));

                meshGeometry3D.Normals.Add(new Vector3D(1, 0, 0));
                meshGeometry3D.Normals.Add(new Vector3D(1, 0, 0));
                meshGeometry3D.Normals.Add(new Vector3D(1, 0, 0));

                meshGeometry3D.Normals.Add(new Vector3D(1, 0, 0));
                meshGeometry3D.Normals.Add(new Vector3D(1, 0, 0));
                meshGeometry3D.Normals.Add(new Vector3D(1, 0, 0));

                mesh.Material = new DiffuseMaterial(Brushes.DarkCyan);
                mesh.BackMaterial = new DiffuseMaterial(Brushes.Red);

                ModelVisual3D verticesInfo = CreateTextLabel3D(
                    String.Format("{0} vertices", meshGeometry3D.TriangleIndices.Count.ToString()),
                    Brushes.DarkGreen,
                    true,
                    0.8,
                    new Point3D(0, -2, 0),
                    new Vector3D(1, 0, 0),
                    new Vector3D(0, 1, 0));

                viewport3D.Children.Add(verticesInfo);
            }
            #endregion 

            #region light setup
            {
                ModelVisual3D lightModelVisual3D = new ModelVisual3D();
                viewport3D.Children.Add(lightModelVisual3D);

                Model3DGroup lightSources = new Model3DGroup();
                lightModelVisual3D.Content = lightSources;

                lightSources.Children.Add(new DirectionalLight(Colors.White, new Vector3D(-1, -2, 1)));
                lightSources.Children.Add(new AmbientLight(Colors.LightGray));
            }
            #endregion

            KeyDown += (s, a) =>
            {
                #region flach
                if (Keyboard.IsKeyDown(Key.Left))
                {
                    angle_flat += Math.PI / 36;

                    camera.Position = new Point3D(
                        distance * Math.Cos(angle_flat),
                        camera.Position.Y,
                        -distance * Math.Sin(angle_flat));

                    camera.LookDirection = new Vector3D(
                        -camera.Position.X,
                        -camera.Position.Y,
                        -camera.Position.Z);
                }

                if (Keyboard.IsKeyDown(Key.Right))
                {
                    angle_flat -= Math.PI / 36;

                    camera.Position = new Point3D(
                        distance * Math.Cos(angle_flat),
                        camera.Position.Y,
                        -distance * Math.Sin(angle_flat));

                    camera.LookDirection = new Vector3D(
                        -camera.Position.X,
                        -camera.Position.Y,
                        -camera.Position.Z);
                }
                #endregion

                #region hoch
                if (Keyboard.IsKeyDown(Key.Up))
                {
                    angle_up += Math.PI / 36;

                    camera.Position = new Point3D(
                        camera.Position.X,
                        distance * Math.Sin(angle_up),
                        distance * Math.Cos(angle_up));

                    camera.LookDirection = new Vector3D(
                        -camera.Position.X,
                        -camera.Position.Y,
                        -camera.Position.Z);
                }

                if (Keyboard.IsKeyDown(Key.Down))
                {
                    angle_up -= Math.PI / 36;

                    camera.Position = new Point3D(
                        camera.Position.X,
                        distance * Math.Sin(angle_up),
                        distance * Math.Cos(angle_up));

                    camera.LookDirection = new Vector3D(
                        -camera.Position.X,
                        -camera.Position.Y,
                        -camera.Position.Z);
                }
                #endregion

                #region depth
                if (Keyboard.IsKeyDown(Key.W))
                {
                    distance -= 0.5;

                    double length = Math.Sqrt(
                        Math.Pow(camera.Position.X, 2)
                        + Math.Pow(camera.Position.Y, 2)
                        + Math.Pow(camera.Position.Z, 2)
                        );

                    camera.Position = new Point3D(
                        camera.Position.X / length * distance,
                        camera.Position.Y / length * distance,
                        camera.Position.Z / length * distance);

                    camera.LookDirection = new Vector3D(
                        -camera.Position.X,
                        -camera.Position.Y,
                        -camera.Position.Z);
                }

                if (Keyboard.IsKeyDown(Key.S))
                {
                    distance += 0.5;

                    double length = Math.Sqrt(
                        Math.Pow(camera.Position.X, 2)
                        + Math.Pow(camera.Position.Y, 2)
                        + Math.Pow(camera.Position.Z, 2)
                        );

                    camera.Position = new Point3D(
                        camera.Position.X / length * distance,
                        camera.Position.Y / length * distance,
                        camera.Position.Z / length * distance);

                    camera.LookDirection = new Vector3D(
                        -camera.Position.X,
                        -camera.Position.Y,
                        -camera.Position.Z);
                }
                #endregion

            };
            
        }


        /// <summary>
        /// Creates a ModelVisual3D containing a text label. http://ericsink.com/wpf3d/4_Text.html
        /// </summary>
        /// <param name="text">The string</param>
        /// <param name="textColor">The color of the text.</param>
        /// <param name="bDoubleSided">Visible from both sides?</param>
        /// <param name="height">Height of the characters</param>
        /// <param name="center">The center of the label</param>
        /// <param name="over">Horizontal direction of the label</param>
        /// <param name="up">Vertical direction of the label</param>
        /// <returns>Suitable for adding to your Viewport3D</returns>
        public static ModelVisual3D CreateTextLabel3D(
            string text,
            Brush textColor,
            bool bDoubleSided,
            double height,
            Point3D center,
            Vector3D over,
            Vector3D up)
        {
            // First we need a textblock containing the text of our label
            TextBlock tb = new TextBlock(new Run(text));
            tb.Foreground = textColor;
            tb.FontFamily = new FontFamily("Arial");

            // Now use that TextBlock as the brush for a material
            DiffuseMaterial mat = new DiffuseMaterial();
            mat.Brush = new VisualBrush(tb);

            // We just assume the characters are square
            double width = text.Length * height;

            // Since the parameter coming in was the center of the label,
            // we need to find the four corners
            // p0 is the lower left corner
            // p1 is the upper left
            // p2 is the lower right
            // p3 is the upper right
            Point3D p0 = center - width / 2 * over - height / 2 * up;
            Point3D p1 = p0 + up * 1 * height;
            Point3D p2 = p0 + over * width;
            Point3D p3 = p0 + up * 1 * height + over * width;

            // Now build the geometry for the sign.  It's just a
            // rectangle made of two triangles, on each side.

            MeshGeometry3D mg = new MeshGeometry3D();
            mg.Positions = new Point3DCollection();
            mg.Positions.Add(p0);    // 0
            mg.Positions.Add(p1);    // 1
            mg.Positions.Add(p2);    // 2
            mg.Positions.Add(p3);    // 3

            if (bDoubleSided)
            {
                mg.Positions.Add(p0);    // 4
                mg.Positions.Add(p1);    // 5
                mg.Positions.Add(p2);    // 6
                mg.Positions.Add(p3);    // 7
            }

            mg.TriangleIndices.Add(0);
            mg.TriangleIndices.Add(3);
            mg.TriangleIndices.Add(1);
            mg.TriangleIndices.Add(0);
            mg.TriangleIndices.Add(2);
            mg.TriangleIndices.Add(3);

            if (bDoubleSided)
            {
                mg.TriangleIndices.Add(4);
                mg.TriangleIndices.Add(5);
                mg.TriangleIndices.Add(7);
                mg.TriangleIndices.Add(4);
                mg.TriangleIndices.Add(7);
                mg.TriangleIndices.Add(6);
            }

            // These texture coordinates basically stretch the
            // TextBox brush to cover the full side of the label.

            mg.TextureCoordinates.Add(new Point(0, 1));
            mg.TextureCoordinates.Add(new Point(0, 0));
            mg.TextureCoordinates.Add(new Point(1, 1));
            mg.TextureCoordinates.Add(new Point(1, 0));

            if (bDoubleSided)
            {
                mg.TextureCoordinates.Add(new Point(1, 1));
                mg.TextureCoordinates.Add(new Point(1, 0));
                mg.TextureCoordinates.Add(new Point(0, 1));
                mg.TextureCoordinates.Add(new Point(0, 0));
            }

            // And that's all.  Return the result.

            ModelVisual3D mv3d = new ModelVisual3D();
            mv3d.Content = new GeometryModel3D(mg, mat); ;
            return mv3d;
        }
    }
}
